﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using BotvaBot.Configuration.Personaj;

namespace BotvaBot.Processor
{
    public class PeriodicProcessor<TArg> : ILoggableProcessManager
    {
        public event EventHandler<LogMessageEventArgs> SendLogMessage;


        public string DefaultLogTypeCode { get; set; }
        protected Thread ThreadWorker { get; set; }
        protected Dictionary<string, Dictionary<string, int>> ActionLog { get; set; }
        protected Random Rnd { get; set; }
        protected PeriodQuantityLimitter MaxQuantityLimitter { get; set; }
        protected Func<TArg, ILoggable, bool> ProcessedAction { get; set; }
        protected TArg Args { get; set; }


        protected volatile bool IsStopped = false;
        protected volatile bool IsPaused = true;
        protected volatile bool IsInvoked = false;
        protected volatile bool IsInited = false;

        public PeriodicProcessor(Func<TArg, ILoggable, bool> action, TArg args)
        {
            ProcessedAction = action;
            Args = args;

            DefaultLogTypeCode = "DEFAULT_LOG";
            Rnd = new Random((int)DateTime.Now.Ticks);
        }


        public void Init(PeriodicProcessorData data)
        {
            if (!IsPaused && !IsStopped)
                return;

            while (IsInvoked) { Thread.Sleep(data.TimeQuantMSec); }

            if(ThreadWorker != null)
            {
                ThreadWorker.Suspend();
                ThreadWorker = null;
            }

            MaxQuantityLimitter = new PeriodQuantityLimitter();
            if (data.MaxExecutingsPeriod.HasValue && data.MaxExecutingsInPeriod.HasValue && data.ReduceFirstTime.HasValue)
            {
                MaxQuantityLimitter.Init(data.MaxExecutingsPeriod.Value, data.MaxExecutingsInPeriod.Value, data.ReduceFirstTime.Value);
            }

            ThreadWorker = new Thread(new ParameterizedThreadStart(Run));
            ThreadWorker.Start(data);
        }

        public void Start()
        {
            MaxQuantityLimitter.Start();
            IsStopped = false;
            IsPaused = false;
        }

        public void Pause()
        {
            IsPaused = true;
        }

        public void Restart()
        {
            IsPaused = false;
        }

        public void Stop()
        {
            IsPaused = true;
            IsStopped = true;
            MaxQuantityLimitter.Stop();
        }

        protected void Run(object state)
        {
            OnSendLogMessage("Processing Start");

            PeriodicProcessorData data = state as PeriodicProcessorData;

            if (data.StartAfterMSec > 0)
            {
                int shift = data.StartAfterMSec;
                Thread.Sleep(shift);
            }

            OnSendLogMessage("Main Loop Start");
            while (!IsStopped)
            {
                DateTime dt = DateTime.Now;

                if (IsPaused)
                {
                    Thread.Sleep(data.TimeQuantMSec);
                    continue;
                }

                DateTime currWorkingTimeStart = new DateTime(dt.Year, dt.Month, dt.Day
                    , data.WorkingTimeStart.Hour, data.WorkingTimeStart.Minute, 0);
                DateTime currWorkingTimeEnd = new DateTime(dt.Year, dt.Month, dt.Day
                    , data.WorkingTimeEnd.Hour, data.WorkingTimeEnd.Minute, 0);
                if (currWorkingTimeEnd <= currWorkingTimeStart)
                    currWorkingTimeEnd = currWorkingTimeEnd.AddDays(1);
                if (dt < currWorkingTimeStart || dt > currWorkingTimeEnd)
                {
                    Thread.Sleep(data.TimeQuantMSec);
                    continue;
                }

                if (!MaxQuantityLimitter.IsEnabled())
                {
                    Thread.Sleep(data.TimeQuantMSec);
                    continue;
                }

                OnSendLogMessage("Action Start");
                DateTime dtStart = DateTime.Now;

                IsInvoked = true;
                bool result = ProcessedAction(Args, this);
                IsInvoked = false;

                DateTime dtEnd = DateTime.Now;
                OnSendLogMessage("Action Stop");

                string resultMsg = string.Format("Action Result = {0}", (result ? "SUCCESS" : "FAILED"));
                OnSendLogMessage(resultMsg);

                if (result)
                    MaxQuantityLimitter.Increment();

                if (dtEnd.Subtract(dtStart).Milliseconds < data.PeriodMSec)
                    Thread.Sleep(data.PeriodMSec - dtEnd.Subtract(dtStart).Milliseconds);

                int pause = Rnd.Next(data.RandomPauseMSec);
                Thread.Sleep(pause);
            }

            OnSendLogMessage("Main Loop Stop");
            OnSendLogMessage("Processing Stop");
        }


        public virtual void OnSendLogMessage(string message)
        {
            OnSendLogMessage(DateTime.Now, DefaultLogTypeCode, message);
        }

        public virtual void OnSendLogMessage(string typeCode, string message)
        {
            OnSendLogMessage(DateTime.Now, typeCode, message);
        }

        public virtual void OnSendLogMessage(DateTime dt, string typeCode, string message)
        {
            EventHandler<LogMessageEventArgs> handler = SendLogMessage;
            if (handler != null)
            {
                var args = new LogMessageEventArgs()
                {
                    TimeStamp = dt,
                    TypeCode = typeCode,
                    Message = message
                };

                handler(this, args);
            }
        }
    }
}
